package com.jh.netty.ws;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketFrameAggregator;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolConfig;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.stream.ChunkedWriteHandler;

public class WebSocketServer {
    // NIO线程组，Reactor线程组，一个用于接受客户端的请求，另一个用于进行SocketChannel的操作
    private final EventLoopGroup bossGroup = new NioEventLoopGroup();
    private final EventLoopGroup workerGroup = new NioEventLoopGroup();

    private final int port; //服务器端端口号

    public WebSocketServer(int port) {
        this.port = port;
    }

    public void run() throws Exception {
        try {
            // 用于NIO服务端启动的辅助类
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    // 这个childHandler参数设置的是我们自己定义的一个通道处理ChildChannelHandler
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            //  websocket连接时，执行以下处理
                            // HttpServerCodec：将请求和应答消息解码为HTTP消息
                            ch.pipeline().addLast("http-codec", new HttpServerCodec());

                            // HttpObjectAggregator：将HTTP消息的多个部分合成一条完整的HTTP消息
                            ch.pipeline().addLast("aggregator", new HttpObjectAggregator(65535));

                            // ChunkedWriteHandler：向客户端发送HTML5文件,文件过大会将内存撑爆
                            ch.pipeline().addLast("http-chunked", new ChunkedWriteHandler());

                            ch.pipeline().addLast("WebSocketAggregator", new WebSocketFrameAggregator(65535));

                            //  若超过80秒未收到约定心跳，则主动断开channel释放资源
                            //            ctx.pipeline().addLast(new IdleStateHandler(80,0,0));
                            //            ctx.pipeline().addLast(new NettyHeartKeeper(NettyHeartKeeper.TYPE_WEBSOCKET));

                            WebSocketServerProtocolConfig conf = WebSocketServerProtocolConfig.newBuilder()
                                    .websocketPath("/ws")
                                    .checkStartsWith(true)
                                    .build();
                            //用于处理websocket, /ws为访问websocket时的uri
                            ch.pipeline().addLast("ProtocolHandler", new WebSocketServerProtocolHandler(conf));
                            ch.pipeline().addLast("socketHandler", new WebSocketHandler());
                        }
                    })
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .childOption(ChannelOption.SO_KEEPALIVE, true);

            System.out.println("Netty WebSocket Server 启 动 ......");
            // 绑定端口，同步等待成功，一直等待到绑定端口成功，返回一个ChannelFuture,类似JDK中的java.util.concurrent.Future
            // 用于异步的通知会调
            ChannelFuture f = b.bind(port).sync();
            // 进行阻塞，等待服务器链路关闭，就退出
            f.channel().closeFuture().sync();
        } finally {
            // 优雅退出，释放线程资源
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
            System.out.println("Netty WebSocket Server 关闭......");
        }
    }

    public static void main(String[] args) throws Exception {
        new WebSocketServer(8080).run();
    }
}
